Repository Public
1import { sql } from "@/lib/db";2import { sameOrigin, visitorHash } from "@/lib/auth";3import { rateLimit } from "@/lib/ratelimit";4import { bad } from "@/lib/api";56/** POST /api/films/:slug/react — anonymous like / unlike, one per visitor per day-hash. */7export async function POST(req: Request, ctx: { params: Promise<{ slug: string }> }) {8 if (!sameOrigin(req)) return bad("bad_origin", 403);9 const visitor = await visitorHash();10 if (!(await rateLimit(`react:${visitor}`, 40, 3600))) return bad("rate_limited", 429);11 const body = (await req.json().catch(() => null)) as { like?: unknown } | null;12 if (!body || typeof body.like !== "boolean") return bad("invalid_body");13 const [film] = await sql<{ id: string }[]>`SELECT id FROM films WHERE slug = ${(await ctx.params).slug} AND status = 'published'`;14 if (!film) return bad("not_found", 404);15 if (body.like) await sql`INSERT INTO reactions (film_id, visitor, kind) VALUES (${film.id}, ${visitor}, 'like') ON CONFLICT DO NOTHING`;16 else await sql`DELETE FROM reactions WHERE film_id = ${film.id} AND visitor = ${visitor} AND kind = 'like'`;17 const [r] = await sql<{ likes: number }[]>`UPDATE films SET likes = (SELECT COUNT(*) FROM reactions WHERE film_id = ${film.id}) WHERE id = ${film.id} RETURNING likes`;18 return Response.json({ likes: r.likes }, { headers: { "Cache-Control": "no-store" } });19}20